package retry

import (
	"time"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/errors/status"
)

type Opts struct {
	// Attempts 重试次数
	Attempts int
	// InitialBackoff 初次回退时间
	InitialBackoff time.Duration
	// MaxBackoff 最大回退时间
	MaxBackoff time.Duration
	// BackoffFactor 回退系数，例如回退系数为2.5
	// 第二次尝试的回退时间为InitialBackoff * 2.5 * 2.5
	BackoffFactor float64
	// RetryableCodes 满足相关状态码才可以重试
	RetryableCodes map[status.Group][]status.Code
}

type Handler interface {
	Required(err error) bool
}

type impl struct {
	opts    Opts
	retries int
}

func New(opts Opts) Handler {
	if len(opts.RetryableCodes) == 0 {
		opts.RetryableCodes = DefaultRetryableCodes
	}
	return &impl{opts: opts}
}

func WithDefaults() Handler {
	return &impl{opts: DefaultOpts}
}

func WithAttempts(attempts int) Handler {
	opts := DefaultOpts
	opts.Attempts = attempts
	return &impl{opts: opts}
}

// Required 是否可以重试
// @param err
// @return bool
func (i *impl) Required(err error) bool {
	if i.retries == i.opts.Attempts {
		return false
	}

	s, ok := status.FromError(err)
	if ok && i.isRetryable(s.Group, s.Code) {
		time.Sleep(i.backoffPeriod())
		i.retries++
		return true
	}

	return false
}

// backoffPeriod 计算回退时间
// @return time.Duration
func (i *impl) backoffPeriod() time.Duration {
	backoff, max := float64(i.opts.InitialBackoff), float64(i.opts.MaxBackoff)
	for j := 0; j < i.retries && backoff < max; j++ {
		backoff *= i.opts.BackoffFactor
	}
	if backoff > max {
		backoff = max
	}

	return time.Duration(backoff)
}

func (i *impl) isRetryable(g status.Group, c int32) bool {
	for group, codes := range i.opts.RetryableCodes {
		if g != group {
			continue
		}
		for _, code := range codes {
			if status.Code(c) == code {
				return true
			}
		}
	}
	return false
}
